package cn.csc.framework.pay.core.client.impl.tl;

import cn.csc.framework.pay.core.client.AbstractPayCodeMapping;
import cn.csc.framework.pay.core.client.PayCommonResult;
import cn.csc.framework.pay.core.client.dto.*;
import cn.csc.framework.pay.core.client.impl.AbstractPayClient;
import cn.csc.framework.pay.core.client.impl.tl.util.SybPayService;
import cn.csc.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import static cn.csc.framework.common.util.json.JsonUtils.toJsonString;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_FORMATTER;

@Slf4j
public abstract class AbstractTLClient extends AbstractPayClient<TLPayClientConfig> {

    /**
     * 网上收银统一下单 网关地址 - 线上
     */
    public static final String SERVER_URL_VSP_PROD = "https://vsp.allinpay.com/";
    /**
     * 通联收银台 网关地址 - 线上
     */
    public static final String SERVER_URL_SYB_PROD = "https://syb.allinpay.com/";

    /**
     * 对账文件下载接口地址 - 线上
     */
    public static final String SERVER_URL_CUS_PROD = "https://cus.allinpay.com/";

    /**
     * 网关地址 - 测试
     */
    public static final String SERVER_URL_TEST = "https://syb-test.allinpay.com/";

    @Getter
    protected SybPayService payService;

    public AbstractTLClient(BigInteger channelId, String channelCode, TLPayClientConfig config, AbstractPayCodeMapping codeMapping) {
        super(channelId, channelCode, config,codeMapping);
    }

    @Override
    protected void doInit() {
        this.config.getServerUrlMap().put("vsp", SERVER_URL_VSP_PROD);
        this.config.getServerUrlMap().put("syb", SERVER_URL_SYB_PROD);
        this.config.getServerUrlMap().put("cus", SERVER_URL_CUS_PROD);
        this.config.getServerUrlMap().put("test", SERVER_URL_TEST);
        payService = new SybPayService(this.config);
    }

    /**
     * 解析支付订单回调数据
     * @param data 通知结果
     * @return 解析结果 PayOrderNotifyRespDTO
     * @throws Exception  解析失败，抛出异常
     */
    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
        Map<String, String> params = data.getParams();
        Map<String, String> bodyObj = HttpUtil.decodeParamMap(data.getBody(), StandardCharsets.UTF_8);
        return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("cusorderid"))
                .channelOrderNo(bodyObj.get("trxid"))
                .tradeStatus("trxstatus").successTime(parseMuchTime(params.get("paytime")))
                .build();
    }

    /**
     * 解析退款订单回调数据
     * @param notifyData  支付退款通知请求数据
     * @return
     */
    @Override
    public PayRefundNotifyRespDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
        Map<String, String> params = strToMap(notifyData.getBody());
        PayRefundNotifyRespDTO notifyDTO = PayRefundNotifyRespDTO.builder().channelOrderNo(params.get("trade_no"))
                .tradeNo(params.get("out_trade_no"))
                .reqNo(params.get("out_biz_no"))
                .status(PayNotifyRefundStatusEnum.SUCCESS)
                .refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
                .rawData(notifyData.getBody())
                .build();
        return notifyDTO;
    }

    @Override
    public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
        if (notifyData.getParams().containsKey("refund_fee")) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
        boolean verifyResult = false;
        try {
            verifyResult =  AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getPrivateKey(), StandardCharsets.UTF_8.name(), "RSA2");
        } catch (AlipayApiException e) {
            log.error("[AbstractTLClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
        }
        return verifyResult;
    }

    @Override
    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
        return null;
    }



    /**
     * 支付宝统一回调参数  str 转 map
     *
     * @param s 支付宝支付通知回调参数
     * @return map 支付宝集合
     */
    public static Map<String, String> strToMap(String s) {
        // TODO @zxy：这个可以使用 hutool 的 HttpUtil decodeParams 方法么？
        Map<String, String> stringStringMap = new HashMap<>();
        // 调整时间格式
        String s3 = s.replaceAll("%3A", ":");
        // 获取 map
        String s4 = s3.replace("+", " ");
        String[] split = s4.split("&");
        for (String s1 : split) {
            String[] split1 = s1.split("=");
            stringStringMap.put(split1[0], split1[1]);
        }
        return stringStringMap;
    }

    /**
     * 通联统一的退款接口
     * @param reqDTO 退款请求 request DTO
     * @return 退款请求 Response
     */
    @Override
    protected abstract PayRefundUnifiedRespDTO doUnifiedBeanRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;

    /**
     * 通联统一的支付接口
     * @param reqDTO
     * @return
     * @throws Throwable
     */
    @Override
    protected abstract PayOrderUnifiedRespDTO doUnifiedBeanOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable;

    /**
     * 获得支付订单信息
     * https://aipboss.allinpay.com/know/devhelp/home.php?id=836
     * @param outTradeNo 外部订单号
     * @return 支付订单信息
     */
    @Override
    protected PayOrderUnifiedRespDTO doGetOrder(String outTradeNo) throws Throwable{
        JSONObject query = payService.query(outTradeNo, "");
        log.info("doGetOrder : {}",query);
        // 查询成功
        if ("SUCCESS".equals(query.getString("retcode")) && "0000".equals(query.getString("trxstatus"))) {
            // 通联返回fintime格式：yyyyMMddHHmmss，转换为yyyy-MM-dd HH:mm:ss
            String finTime = LocalDateTimeUtil.formatNormal(LocalDateTimeUtil.parse(query.getString("fintime"), PURE_DATETIME_FORMATTER));
            return PayOrderUnifiedRespDTO.successOf(query.getString("trxid"), "", finTime,
                    query.getString("trxid"), query.getString("reqsn"), toJsonString(query), null)
                    .setTradeStatus(query.getString("trxstatus"));
        }
        PayOrderUnifiedRespDTO respDTO = PayOrderUnifiedRespDTO.closedOf(query.getString("retcode"), query.getString("errmsg"), "", toJsonString(query))
                .setTradeStatus(query.getString("trxstatus"));
        return respDTO;
    }


    @Override
    public Object parseNotify(PayNotifyReqDTO rawNotify) {
        // 1. 校验回调数据
        String body = rawNotify.getBody();
        Map<String, String> params = rawNotify.getParams();
        Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);

        // 2.1 退款的情况
        if (bodyObj.containsKey("refund_fee")) {
            return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
                    .tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
                    .status(PayNotifyRefundStatusEnum.SUCCESS)
                    .refundSuccessTime(parseTime(params.get("gmt_refund")))
                    .rawData(body)
                    .build();
        }
        // 2.2 支付的情况
        return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("cusorderid"))
                .channelOrderNo(bodyObj.get("trxid"))
                .tradeStatus("trxstatus")
                .successTime(parseMuchTime(params.get("paytime")))
                .rawData(body)
                .build();

    }


    protected LocalDateTime parseMuchTime(String str) {
        LocalDateTime pureDate = LocalDateTimeUtil.parse(str, PURE_DATETIME_FORMATTER);
        String normalDateStr = LocalDateTimeUtil.format(pureDate,NORM_DATETIME_FORMATTER);
        return LocalDateTimeUtil.parse(normalDateStr, NORM_DATETIME_FORMATTER);
    }
}
